#ifndef ANIMATIONSYSTEM_H
#define ANIMATIONSYSTEM_H
#pragma once

#include <vector>
#include <fstream>
using std::ifstream;

#include "vec3.h"
#include "matrix4.h"

////////////////////////////////////////////////////////////////
// Stores the state of the animation at a given time
////////////////////////////////////////////////////////////////
template <typename T>
struct KeyFrame
{
	// The time this keyframe happens at
	float keyTime;
	float tweenTime;
	unsigned int numNodes;

	// The transforms for this keyframe
	std::vector<T> nodeVec;

	KeyFrame() : keyTime(0.0f), tweenTime(0.0f), numNodes(0) {} 
};

////////////////////////////////////////////////////////////////
// Stores the Mesh data
////////////////////////////////////////////////////////////////
class Mesh
{
	friend class Model;
private:

	std::vector<float3>			m_vVertices;
	std::vector<float3>			m_vNormals;

public:
	std::vector<float3>			&GetVertices(void)	{ return m_vVertices; }
	std::vector<float3>			&GetNormals(void)	{ return m_vNormals; }
};

////////////////////////////////////////////////////////////////
// Stores the Multiple Meshes of a Model
////////////////////////////////////////////////////////////////
class Model
{
private:
	std::vector<Mesh>	m_vMeshes;
public:
	////////////////////////////////////////////////////////////////
	// Loads a mesh from a file
	//
	// Parameters
	// string fileName - the name of the mesh file
	////////////////////////////////////////////////////////////////
	void LoadMeshsFromFile(std::string fileName)
	{
		ifstream readObject;
		readObject.open(fileName, std::ios_base::in | std::ios_base::binary);

		if (!readObject.is_open())
			return;

		unsigned int numMesh = 0;

		// Mesh number
		readObject.read((char*)&numMesh, sizeof(numMesh));
		m_vMeshes.reserve(numMesh);

		unsigned int meshVertNum = 0;
		float3 tempReadFloat;

		// Loop through all meshes
		for (unsigned int meshCount = 0; meshCount < numMesh; ++meshCount)
		{
			Mesh newMesh;

			// Vert number
			readObject.read((char*)&meshVertNum, sizeof(meshVertNum));

			// Reserve vector space
			newMesh.m_vVertices.reserve(meshVertNum);
			newMesh.m_vNormals.reserve(meshVertNum);

			// Verts
			for (unsigned int vertCount = 0; vertCount < meshVertNum; ++vertCount)
			{
				readObject.read((char*)&tempReadFloat, sizeof(tempReadFloat));
				newMesh.m_vVertices.push_back(tempReadFloat);
			}

			// Normals
			for (unsigned int vertCount = 0; vertCount < meshVertNum; ++vertCount)
			{
				readObject.read((char*)&tempReadFloat, sizeof(tempReadFloat));
				newMesh.m_vNormals.push_back(tempReadFloat);
			}

			// Add the mesh
			m_vMeshes.push_back(newMesh);
		}

		readObject.close();
	}

	const std::vector<Mesh>	&GetMeshes(void) { return m_vMeshes; }

	const unsigned int GetMeshNum() { return m_vMeshes.size(); }
	Mesh const* GetMesh(unsigned int _index) { return &(m_vMeshes[_index]); }
};

////////////////////////////////////////////////////////////////
// Stores the animation data
////////////////////////////////////////////////////////////////
template <typename T>
class Animation
{	
	template <class T>
	friend class Interpolator;

private:
	unsigned int numKeyFrames;
	float duration;
	std::vector<KeyFrame<T>> keyFrames;

public:
	Animation(void){ }
	~Animation(void){ }

	////////////////////////////////////////////////////////////////
	// Loads the animation from a file
	//
	// Parameters
	// string fileName - the name of the animation file
	////////////////////////////////////////////////////////////////
	void LoadFromFile(std::string fileName)
	{
		std::ifstream readObject;
		readObject.open(fileName, std::ios_base::in | std::ios_base::binary);

		if (!readObject.is_open())
			return;

		// Total keyframes
		readObject.read((char*)&numKeyFrames, sizeof(numKeyFrames));

		// Keyframe node number
		unsigned int frameNodeNum = 0;
		readObject.read((char*)&frameNodeNum, sizeof(frameNodeNum));

		// Duration
		readObject.read((char*)&duration, sizeof(duration));

		keyFrames.reserve(numKeyFrames);

		for (unsigned int i = 0; i < numKeyFrames; ++i)
		{
			KeyFrame<T> tempFrame;

			// Keyframe time
			readObject.read((char*)&tempFrame.keyTime, sizeof(tempFrame.keyTime));
		
			// Vector reservation
			tempFrame.numNodes = frameNodeNum;
			tempFrame.nodeVec.reserve(tempFrame.numNodes);
		
			for (unsigned int nodeCount = 0; nodeCount < tempFrame.numNodes; ++nodeCount)
			{
				T tempNode;

				readObject.read((char*)&tempNode, sizeof(tempNode));
				tempFrame.nodeVec.push_back(tempNode);
			}

			keyFrames.push_back(tempFrame);
		}

		readObject.close();

		// Calculate tween time
		for (unsigned int i = 0; i < numKeyFrames-1; ++i)
		{
			keyFrames[i].tweenTime = (keyFrames[i+1].keyTime - keyFrames[i].keyTime);
		}

		// Tween time - last object
		keyFrames[numKeyFrames-1].tweenTime = (duration - keyFrames[numKeyFrames-1].keyTime);
	}
};

template <typename T>
class Interpolator
{
private:
	float currTime;
	Animation<T> const* animation;
	KeyFrame<T> currKeyFrame;

public:
	Interpolator(void) : currTime(0.0f), animation(NULL) {}
	~Interpolator(void){ }

	////////////////////////////////////////////////////////////////
	// Attaches an animation to the interpolator
	//
	// Parameters
	// const Animation *inAnimation - the animation to attach to
	////////////////////////////////////////////////////////////////
	template <typename T>
	void SetAnimation(const Animation<T> *inAnimation)
	{
		animation = inAnimation;
		currKeyFrame = animation->keyFrames[0];
	}

	////////////////////////////////////////////////////////////////
	// Adds time to the interpolator
	//
	// Parameters
	// float t - the time to add
	////////////////////////////////////////////////////////////////
	//template <typename T>
	void AddTime(float t) { currTime += t; }

	////////////////////////////////////////////////////////////////
	// Sets the interpolators time
	//
	// Parameters
	// float t - the time to set to
	////////////////////////////////////////////////////////////////
	//template <typename T>
	void SetTime(float t) { currTime = t; }

	////////////////////////////////////////////////////////////////
	// Interpolates between the animations key-frames based on the 
	// interolator's current time
	////////////////////////////////////////////////////////////////
	//template <typename T>
	void Process(void)
	{
		float lambda = 0.0f;

		// Error Check Time
		if (currTime < 0.0f)
			currTime += this->animation->duration;

		if (currTime > this->animation->duration)
			currTime -= this->animation->duration;

		// Find current frame
		for (unsigned int i = 0; i < this->animation->numKeyFrames; ++i)
		{
			if (currTime < (animation->keyFrames[i].keyTime + animation->keyFrames[i].tweenTime))
			{
				// Set frame
				const KeyFrame<T>& frame1 = animation->keyFrames[i];

				// Roll over the index
				unsigned int index = i+1;
				if (index >= animation->numKeyFrames)
					index = 0;

				const KeyFrame<T>& frame2 = animation->keyFrames[index];

				lambda = (currTime - frame1.keyTime) / frame1.tweenTime;

				for (unsigned int j = 0; j < this->currKeyFrame.numNodes; ++j)
				{
					// Interpolate into currKeyFrame
					currKeyFrame.nodeVec[j] = interpolate(frame1.nodeVec[j], frame2.nodeVec[j], lambda);
				}

				break;
			}
		}
	}
	
	// Return the current Keyframe
	//template <typename T>
	KeyFrame<T>* GetCurrKeyFrame()	{ return &currKeyFrame; }
};

#endif